#include "src/pbc/transform/portable-lazy-transformer.h"

#include "include/v8-version.h"
#include "src/pbc/transform/portable-transformer.h"

namespace v8 {
namespace internal {
using namespace portable;

void LazyPbcTransformer::LazyRun() {
  uint32_t func_id = static_cast<uint32_t>(current_sfi_->function_literal_id());
  DCHECK(func_id != v8::internal::kFunctionLiteralIdTopLevel);

  (void)TransformFunction(func_id);
}

// just transform current sfi
Handle<SharedFunctionInfo> LazyPbcTransformer::TransformFunction(
    uint32_t func_id) {
  DCHECK(func_id < pbc_script_.NumberOfFunctions());
  DCHECK(!current_sfi_->is_compiled());

  TRACE_EVENT1(TRACE_DISABLED_BY_DEFAULT("v8.compile"),
               "V8.AOT.LazyTransformFunction", "func_id", func_id);

  PbcFunction pbc_func = pbc_script_.GetFunction(func_id);

  if (i::v8_flags.log_pbc) {
    std::cout << "========== Start Lazy TransformFunction: ";
    StringRef func_name = pbc_func.GetName();
    if (func_name.Kind() == PbcValueKind::kU8String) {
      std::cout << pbc_script_.GetString(func_name);
    } else {
      std::u16string_view str_view = pbc_script_.GetU16String(func_name);
      for (auto c : str_view) {
        std::wcout << static_cast<wchar_t>(c);
      }
    }
    std::cout << ", func_id: " << func_id << std::endl;
  }

  // 1.lazy transform has sfi, just transform bytecode
  DCHECK(!current_sfi_->is_compiled());
  Handle<BytecodeArray> bytecode_array = TransformBytecode(func_id);
  initSourcePositionTable(bytecode_array);

  // 2.set bytecode array
  current_sfi_->set_bytecode_array(*bytecode_array);
  CHECK(static_cast<uint32_t>(bytecode_array->parameter_count()) ==
        pbc_func.GetParameterCount());
  return current_sfi_;
}

Handle<TrustedFixedArray> LazyPbcTransformer::TransformConstantPool(
    uint32_t func_id) {
  TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.compile"),
               "V8.AOT.LazyTransformConstantPool");
  PbcFunction pbcFunc = pbc_script_.GetFunction(func_id);
  PbcConstantTable constantTable = pbcFunc.GetConstantTable(pbc_script_);
  uint32_t numEntries = constantTable.NumberOfEntries();

#if V8_MAJOR_VERSION == 11
  auto fixed_array =
      isolate_.factory()->NewFixedArray(static_cast<int>(numEntries));
#else
  auto fixed_array = isolate_.factory()->NewTrustedFixedArray(
      static_cast<int>(numEntries), AllocationType::kTrusted);
#endif
  int array_index = 0;
  bool is_short_value = constantTable.IsShortValue();
  for (uint32_t i = 0; i < numEntries; ++i) {
    Handle<Object> value;
    if (is_short_value) {
      const PbcShortValue& entry = constantTable.ShortEntries()[i];
      value = PbcShortValueToV8Value(entry);
    } else {
      const PbcValue& entry = constantTable.Entries()[i];
      value = PbcValueToV8Value(entry);
    }
    fixed_array->set(array_index++, *value);
  }
  // TODO need consider padding that leaves holes where reservations led to
  // unused slots?
  DCHECK_GE(array_index, fixed_array->length());
  return fixed_array;
}

Handle<BytecodeArray> LazyPbcTransformer::TransformBytecode(uint32_t func_id) {
  TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.compile"),
               "V8.AOT.LazyTransformBytecode");

  PbcFunction pbcFunc = pbc_script_.GetFunction(func_id);
  PbcBytecode pbcBytecode = pbcFunc.GetBytecode(pbc_script_);

  PbcConstantTable constantTable = pbcFunc.GetConstantTable(pbc_script_);
  Handle<TrustedFixedArray> constantPool = TransformConstantPool(func_id);
  portable::PBCLowerItems items(&isolate_, current_sfi_, pbcFunc, pbcBytecode,
                                constantTable, constantPool);
  return portable::PBCLower::Lower(items);
}

// todo:support block scope
Handle<ScopeInfo> LazyPbcTransformer::GetOrTransformScope(uint32_t scope_id) {
  DCHECK(scope_id != ScopeRef::Invalid().GetScopeId());
  if (scope_id == static_cast<uint32_t>(current_sfi_->function_literal_id()))
    return current_scope_info_;

  TRACE_EVENT1(TRACE_DISABLED_BY_DEFAULT("v8.compile"),
               "V8.AOT.LazyTransformScope", "scope_id", scope_id);

  if (i::v8_flags.log_pbc) {
    std::cout << "lazy TransformScope:\n";
    // pbc_scope_vec_[scope_id].Dump(pbc_script_);
    pbc_script_.GetPbcScope(scope_id).Dump(pbc_script_);
  }

  // create ScopeInfo
  PbcScope pbcScope =
      pbc_script_.GetPbcScope(scope_id);  // pbc_scope_vec_[scope_id];
  uint32_t pbcOuterScopeId = pbcScope.GetOuterScopeId();
  DCHECK(pbcOuterScopeId != ScopeRef::Invalid().GetScopeId());
  CHECK(pbcOuterScopeId ==
        static_cast<uint32_t>(current_sfi_->function_literal_id()));
  MaybeHandle<ScopeInfo> outerScopeInfo = current_scope_info_;
  outerScopeInfo.Check();
  Handle<ScopeInfo> scopeInfo = ScopeInfo::Create(&isolate_, &zone_, pbcScope,
                                                  pbc_script_, outerScopeInfo);

  return scopeInfo;
}

Handle<SharedFunctionInfo> LazyPbcTransformer::GetOrCreateFuncData(
    uint32_t func_id) {
  {
    // try from script
    CHECK_LT(func_id, getScriptInfosLength());
#if V8_MAJOR_VERSION == 11
    MaybeObject shared = script_->shared_function_infos().Get(func_id);
    HeapObject heap_object;
    if (shared->GetHeapObject(&heap_object) && !heap_object.IsUndefined()) {
      return handle(SharedFunctionInfo::cast(heap_object), &isolate_);
    }
#else
    Tagged<MaybeObject> shared = script_->infos()->get(func_id);
    Tagged<HeapObject> heap_object;
    if (shared.GetHeapObject(&heap_object) && !IsUndefined(heap_object)) {
      return handle(Cast<SharedFunctionInfo>(heap_object), &isolate_);
    }
#endif
  }

  // create sfi
  PbcFunction pbc_func = pbc_script_.GetFunction(func_id);
  Handle<String> func_name =
      UtilsInternalizeStringRef(pbc_script_, isolate_, pbc_func.GetName());
  FunctionKind kind = ConvertFunctionKind(pbc_func.GetFunctionKind());

#if V8_MAJOR_VERSION == 11
  Handle<SharedFunctionInfo> sfi;
#else
  DirectHandle<SharedFunctionInfo> sfi;
#endif

  int16_t len = pbc_func.GetLength();
  DCHECK(len >= 0);
  MaybeHandle<String> maybe_name =
      pbc_func.IsNullName() ? MaybeHandle<String>() : func_name;
#if V8_MAJOR_VERSION == 11
  sfi = isolate_.factory()->NewSharedFunctionInfoForBuiltin(
      maybe_name, Builtin::kCompileLazy, kind);
  sfi->set_length(len);
#else
  sfi = isolate_.factory()->NewSharedFunctionInfoForBuiltin(
      maybe_name, Builtin::kCompileLazy, len, kDontAdapt, kind);
#endif

  Handle<SharedFunctionInfo> result;

  DCHECK(func_id < getScriptInfosLength());
#if V8_MAJOR_VERSION == 11
  script_->shared_function_infos().Set(
      func_id, MaybeObject::MakeWeak(MaybeObject::FromObject(*sfi)));
  result = sfi;
#else
  script_->infos()->set(func_id, *sfi);
  result = sfi.operator->();
#endif
  initSharedFunctionInfo(func_id, result);
  return result;
}

}  // namespace internal
}  // namespace v8
